//
//  DrawerVC.swift
//  RaveLand
//
//  Created by CS001 on 2021/3/30.
//  Copyright © 2021 CSLY. All rights reserved.
//

import UIKit

open class DrawerVC: PannelVC {
    public var slideFrom: SlideFrom = .left
    public var slideStop: SlideStop = .topLeft
    
    public init(
        panelView: UIView,
        panelSize: CGSize,
        slideFrom: SlideFrom,
        slideStop: SlideStop
    ) {
        super.init(panelView: panelView, panelSize: panelSize)
        self.slideFrom = slideFrom
        self.slideStop = slideStop
    }
    
    public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    }
    
    public required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    @objc private func gesturePaned(_ sender: UIPanGestureRecognizer) {
        panelViewDidPan(sender, translation: sender.translation(in: sender.view))
    }
    
    private lazy var maskColorAlpha = maskColor.alpha
    private var startPoint: CGPoint = .zero
    private var endPoint: CGPoint = .zero
    open func panelViewDidPan(_ sender: UIPanGestureRecognizer, translation: CGPoint) {
        guard let panel = sender.view, shouldEnablePanelPaning() else {
            return
        }
        var rect = panel.frame
        var y = rect.origin.y
        var x = rect.origin.x
        var a = maskColorAlpha
        switch sender.state
        {
        case .began:
            startPoint = slideOriginalPoint()
            endPoint = slideFinalPoint()
        case .changed:
            y += translation.y
            x += translation.x
            switch slideFrom {
            case .top:
                y = min(endPoint.y, max(startPoint.y, y))
            case .bottom:
                y = min(startPoint.y, max(endPoint.y, y))
            case .left:
                x = min(endPoint.x, max(startPoint.x, x))
            case .right:
                x = min(startPoint.x, max(endPoint.x, x))
            default: break
            }
            let yMoved = abs(y-endPoint.y)
            let xMoved = abs(x-endPoint.x)
            CATransaction.setDisableActions(true)
            CATransaction.begin()
            if slideFrom == .top || slideFrom == .bottom {
                rect.origin.y = y
                a = (1 - yMoved/ySlideMaxDistance) * maskColorAlpha
                a = min(maskColorAlpha, max(0, a))
            }
            else {
                rect.origin.x = x
                a = (1 - xMoved/xSlideMaxDistance) * maskColorAlpha
                a = min(maskColorAlpha, max(0, a))
            }
            panel.frame = rect
            view.backgroundColor = maskColor.alpha(a)
            sender.setTranslation(.zero, in: sender.view)
            CATransaction.commit()
        case .failed, .ended:
            let velocity = sender.velocity(in: sender.view)
            var halfMoved = abs(y-endPoint.y) > ySlideMaxDistance/2
            var velocityOvered = false
            if (slideFrom == DrawerVC.SlideFrom.left || slideFrom == DrawerVC.SlideFrom.right) {
                halfMoved = abs(x-endPoint.x) > xSlideMaxDistance/2
            }
            switch slideFrom {
            case .left:
                velocityOvered = velocity.x < -200
            case .right:
                velocityOvered = velocity.x > 200
            case .top:
                velocityOvered = velocity.y < -200
            case .bottom:
                velocityOvered = velocity.y > 200
            default: break
            }
            switch (velocityOvered, halfMoved) {
            case (true, _), (_, true):
                close()
            default:
                animate(in: true, for: panel)
            }
        default: break
        }
    }
    
    open func shouldEnablePanelPaning() -> Bool {
        switch (slideFrom, slideStop) {
        case (.custom(_), _), (_, .custom(_)):
            return false
        default:
            return true
        }
    }
    
    open override func setupWrapperView(_ wrapperView: UIView) {
        switch (slideFrom, slideStop) {
        case (.custom(_), _), (_, .custom(_)): break
        default:
            let gesture = UIPanGestureRecognizer(target: self, action: #selector(gesturePaned(_:)))
            gesture.maximumNumberOfTouches = 1
            wrapperView.addGestureRecognizers([
                gesture
            ])
        }
        wrapperView.frame = .init(origin: slideOriginalPoint(), size: panelSize)
    }
    open override func updateWrapperView(_ wrapperView: UIView) {
        wrapperView.frame = .init(origin: slideFinalPoint(), size: panelSize)
        startPoint = slideOriginalPoint()
        endPoint = slideFinalPoint()
    }
    
    private func animateDuration(_ isIn: Bool) -> CGFloat {
        let xScale = xSlideMaxDistance/UIScreen.main.bounds.width
        let yScale = ySlideMaxDistance/UIScreen.main.bounds.height
        var duration: CGFloat = 0
        switch slideFrom {
        case .left, .right:
            duration = isIn ? 0.35 * xScale : 0.3 * xScale
        case .top, .bottom:
            duration = isIn ? 0.4 * yScale : 0.35 * yScale
        default: break
        }
        return max(0.25, duration)
    }
    
    public override func animate(in isIn: Bool = true, for wrapperView: UIView, completion: (() -> Void)? = nil)
    {
        var rect = wrapperView.frame
        let toOrigin = isIn ? slideFinalPoint() : slideOriginalPoint()
        if rect.origin.equalTo(toOrigin) {
            return
        }
        let duration = animateDuration(isIn)
        let alpha: CGFloat = isIn ? 1 : 0
        rect.origin = toOrigin
        CATransaction.disableActions()
        CATransaction.begin()
        UIView.animate(
            withDuration: duration,
            delay: 0,
            options: [.curveEaseOut]
        ) {
            self.view.alpha = alpha
            self.view.backgroundColor = self.maskColor
            wrapperView.frame = rect
            wrapperView.alpha = alpha
        } completion: { [weak self] in
            guard $0, let self = self else { return }
            completion?()
            self.isShowing = isIn
        }
        CATransaction.commit()
    }
    
}

extension DrawerVC {
    private var xSlideMaxDistance: CGFloat {
        return abs(slideFinalPoint().x - slideOriginalPoint().x)
    }
    private var ySlideMaxDistance: CGFloat {
        return abs(slideFinalPoint().y - slideOriginalPoint().y)
    }
}

extension DrawerVC {
    public enum SlideFrom: Equatable {
        case left, right, top, bottom
        case custom(point: CGPoint)
        static public func == (lhs: Self, rhs: Self) -> Bool {
            switch (lhs, rhs) {
            case (.custom(let p1), .custom(let p2)):
                return p1.equalTo(p2)
            default:
                return "\(lhs)" == "\(rhs)"
            }
        }
    }
    
    public enum SlideStop: Equatable {
        case topLeft, topMiddle, topRight
        case leftMiddle, center, rightMiddle
        case bottomLeft, bottomMiddle, bottomRight
        case custom(point: CGPoint)
        static public func == (lhs: Self, rhs: Self) -> Bool {
            switch (lhs, rhs) {
            case (.custom(let p1), .custom(let p2)):
                return p1.equalTo(p2)
            default:
                return "\(lhs)" == "\(rhs)"
            }
        }
    }
    
    public func slideFinalPoint() -> CGPoint {
        let W = UIScreen.main.bounds.width
        let H = UIScreen.main.bounds.height
        let w = panelSize.width
        let h = panelSize.height
        
        switch slideStop {
        case .topLeft: return .zero
        case .topMiddle: return .init(x: (W-w)/2, y: 0)
        case .topRight: return .init(x: W-w, y: 0)
        case .leftMiddle: return .init(x: 0, y: (H-h)/2)
        case .center: return .init(x: (W-w)/2, y: (H-h)/2)
        case .rightMiddle: return .init(x: W-w, y: (H-h)/2)
        case .bottomLeft: return .init(x: 0, y: H-h)
        case .bottomMiddle: return .init(x: (W-w)/2, y: H-h)
        case .bottomRight: return .init(x: W-w, y: H-h)
        case .custom(let point): return point
        }
    }
    
    public func slideOriginalPoint() -> CGPoint {
        let W = UIScreen.main.bounds.width
        let H = UIScreen.main.bounds.height
        let w = panelSize.width
        let h = panelSize.height
        
        switch slideFrom {
        case .left:
            switch slideStop {
            case .topLeft, .topMiddle, .topRight: return .init(x: -w, y: 0)
            case .leftMiddle, .center, .rightMiddle: return .init(x: -w, y: (H-h)/2)
            case .bottomLeft, .bottomMiddle, .bottomRight: return .init(x: -w, y: H-h)
            case .custom(_): break
            }
        case .right:
            switch slideStop {
            case .topLeft, .topMiddle, .topRight: return .init(x: W, y: 0)
            case .leftMiddle, .center, .rightMiddle: return .init(x: W, y: (H-h)/2)
            case .bottomLeft, .bottomMiddle, .bottomRight: return .init(x: W, y: H-h)
            case .custom(_): break
            }
        case .top:
            switch slideStop {
            case .topLeft, .leftMiddle, .bottomLeft: return .init(x: 0, y: -h)
            case .topMiddle, .center, .bottomMiddle: return .init(x: (W-w)/2, y: -h)
            case .topRight, .rightMiddle, .bottomRight: return .init(x: W-w, y: -h)
            case .custom(_): break
            }
        case .bottom:
            switch slideStop {
            case .topLeft, .leftMiddle, .bottomLeft: return .init(x: 0, y: H)
            case .topMiddle, .center, .bottomMiddle: return .init(x: (W-w)/2, y: H)
            case .topRight, .rightMiddle, .bottomRight: return .init(x: W-w, y: H)
            case .custom(_): break
            }
        case .custom(let point): return point
        }
        return .zero
    }
}
